﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Practices.ServiceLocation;
using Pap.Data;
using Pap.Data.Abstraction.Utils;
using Utils;

namespace Pap.Services.WorkflowEngine
{
    /// <summary>
    /// 
    /// </summary>
    public class WorkflowService : IWorkflowService
    {
        /// <summary>
        /// 
        /// </summary>
        IWorkFlow _WorkFlow;
        /// <summary>
        /// 
        /// </summary>
        IDatabase _Database;

        /// <summary>
        /// Initializes a new instance of the WorkflowService <see cref="WorkflowService"/> class.
        /// </summary>
        /// <param name="workFlow">The work flow.</param>
        /// <param name="database">The database.</param>
        public WorkflowService(IWorkFlow workFlow, IDatabase database)
        {
            _WorkFlow = workFlow;
            _Database = database;
        }

        /// <summary>
        /// Creates new process instance from a process.
        /// </summary>
        /// <param name="process">The process.</param>
        /// <param name="entityID">The entity ID.</param>
        /// <param name="userID">The user ID.</param>
        /// <param name="notes">The notes.</param>
        public void CreateProcessInstance(Process process, Guid entityID, Guid userID, string notes)
        {
            Guid processID = Enums.ToString(process).ToGuid();
            //Create a row in the Processinsatance            
            IwfProcessInstance wfProcessInstance = ServiceLocator.Current.GetInstance<IwfProcessInstance>();
            wfProcessInstance.ProcessInstanceID = Guid.NewGuid();
            wfProcessInstance.ProcessID = processID;
            wfProcessInstance.EntityID = entityID;
            wfProcessInstance.UserID = userID;
            wfProcessInstance.CreationDate = DateTime.Now;
            _WorkFlow.Insert<IwfProcessInstance>(wfProcessInstance);

            //Create a row in Step instance as the first step of this process
            IwfStepInstance wfStepInstance = ServiceLocator.Current.GetInstance<IwfStepInstance>();
            wfStepInstance.StepInstanceID = Guid.NewGuid();
            wfStepInstance.ProcessInstanceID = wfProcessInstance.ProcessInstanceID;
            wfStepInstance.UserID = userID;
            wfStepInstance.Comments = notes.IsNullOrEmpty() ? null : notes;

            wfStepInstance.StepID = (from step in _WorkFlow.wfSteps
                                     where step.ProcessID == processID && step.IsFirstStep
                                     select step.StepID).Single();
            wfStepInstance.ActionTypeID = Enums.ToString(ActionTypes.Create).ToGuid();

            wfStepInstance.CreationDate = DateTime.Now;
            _WorkFlow.Insert<IwfStepInstance>(wfStepInstance);

        }

        /// <summary>
        /// Moves the specified process instance form its current step according to action.
        /// </summary>
        /// <param name="stepAction">The step action.</param>
        /// <param name="processInsnatceID">The process insnatce ID.</param>
        /// <param name="userID">The user ID.</param>
        /// <param name="notes">The notes.</param>
        /// <param name="completionDate">The completion date.</param>
        public void Move(ActionTypes stepAction, Guid processInsnatceID, Nullable<Guid> userID, string notes, Nullable<DateTime> completionDate)
        {
            IwfAction action;
            var currentStepInstance = (from stepinstance in _WorkFlow.wfStepInstances
                                       where stepinstance.ProcessInstanceID == processInsnatceID
                                       orderby stepinstance.CreationDate descending
                                       select stepinstance).First();



            //If this action is not available for the current step throw exception.

            var actionTypeID = Utils.Enums.ToString(stepAction).ToGuid();

            try
            {
                action = (from a in currentStepInstance.wfStep.wfActions
                          where a.ActionTypeID == actionTypeID
                          select a).First();
            }
            catch (Exception exp)
            {
                throw new Exception("Action is not available for the current step");
                string s = exp.Message;
            }

            //If this action is available but the user has no authorization for it throw exce ption
            //if (!_database.Users.Where(user => user.userID == userID).Single().Permissions.Select(wfSt => wfSt.PermissionID).Any(userPermissionID => currentStepInstance.wfStep.wfStepPermissions.Select(wfs => wfs.PermissionID).Contains(userPermissionID)))
            if (!currentStepInstance.wfStep.wfStepPermissions.Select(wfs => wfs.PermissionID).Any(wfStepPermissionID => _Database.Users.Where(user => user.UserID == userID).Single().Permissions.Select(wfSt => wfSt.PermissionID).Contains(wfStepPermissionID)))
            {
                throw new Exception("The user has no authorization");
            }

            //If this action is available add a new row in stepinstance with the available fields

            IwfStepInstance wfStepInstance = ServiceLocator.Current.GetInstance<IwfStepInstance>();
            wfStepInstance.StepInstanceID = Guid.NewGuid();
            wfStepInstance.ActionTypeID = actionTypeID;
            wfStepInstance.StepID = action.NextStepID;
            wfStepInstance.UserID = userID;
            wfStepInstance.CreationDate = DateTime.Now;
            wfStepInstance.CompletionDate = completionDate;
            wfStepInstance.Comments = notes.IsNullOrEmpty() ? null : notes;
            wfStepInstance.PreviousStepInstanceID = currentStepInstance.StepInstanceID;
            wfStepInstance.ProcessInstanceID = processInsnatceID;
            _WorkFlow.Insert<IwfStepInstance>(wfStepInstance);

        }

        /// <summary>
        /// Gets the current step for the processInstance.
        /// </summary>
        /// <param name="processInsanaceID">The process insanace ID.</param>
        /// <returns></returns>
        public Step GetStep(Guid processInsanaceID)
        {
            // Get the current stepinstance of this process.  
            var step = (from SI in _WorkFlow.wfStepInstances
                        where SI.ProcessInstanceID == processInsanaceID
                        orderby SI.CreationDate descending
                        select SI).First();
            Dictionary<string, int> dicActionTypes = Enums.ToNameKeyPaires(typeof(ActionTypes));
            return new Step
            {
                CreationDate = step.CreationDate,
                Description = step.wfStep.Description,
                DisplayName = step.wfStep.DisplayName,
                IsLastStep = step.wfStep.IsLastStep,
                PreviousNotes = step.Comments,
                ActionType = (ActionTypes)dicActionTypes[step.ActionTypeID.ToString()],
                User = step.User,
                PreviousStepAction = step.PreviousStepInstance != null ? step.PreviousStepInstance.wfActionType : null,
                CompletionDate = step.CompletionDate
            };
        }

        /// <summary>
        /// Sends the processinstence back to the previous step .
        /// </summary>
        /// <param name="processInsanaceID">The process insanace ID.</param>
        public void SendBack(Guid processInsanaceID)
        {
            var step = (from SI in _WorkFlow.wfStepInstances
                        where SI.ProcessInstanceID == processInsanaceID
                        orderby SI.CreationDate descending
                        select SI).First();

            _WorkFlow.Delete(step);
        }

        /// <summary>
        /// Gets the available actions for the current entity's step.
        /// </summary>
        /// <param name="EntityID">The entity ID.</param>
        /// <returns></returns>
        public IEnumerable<ActionTypes> GetAvailableActionsByEntityId(Guid EntityID)
        {
            var currentStepInstance = (from stepinstance in _WorkFlow.wfStepInstances
                                       where stepinstance.ProcessInstanceID == (from processInstance in _WorkFlow.wfProcessInstances
                                                                                where processInstance.EntityID == EntityID
                                                                                select processInstance.ProcessInstanceID).Single()
                                       orderby stepinstance.CreationDate descending
                                       select stepinstance).First();

            Dictionary<string, int> dic = Enums.ToNameKeyPaires(typeof(ActionTypes));
            return currentStepInstance.wfStep.wfActions.Select(action => (ActionTypes)dic[action.ActionTypeID.ToString()]);
        }

        /// <summary>
        /// Gets the available actions by step id.
        /// </summary>
        /// <param name="StepId">The step id.</param>
        /// <returns></returns>
        public IEnumerable<ActionTypes> GetAvailableActionsByStepId(Guid StepId)
        {
            var currentStep = this._WorkFlow.wfSteps.Single(t => t.StepID == StepId);
            Dictionary<string, int> dic = Enums.ToNameKeyPaires(typeof(ActionTypes));
            return currentStep.wfActions.Select(action => (ActionTypes)dic[action.ActionTypeID.ToString()]);
        }

        /// <summary>
        /// Gets the available actions by step.
        /// </summary>
        /// <param name="Step">The step.</param>
        /// <returns></returns>
        public IEnumerable<ActionTypes> GetAvailableActionsByStep(Steps Step)
        {
            var currentStep = this._WorkFlow.wfSteps.Single(t => t.StepID.ToString() == Enums.ToString(Step));
            Dictionary<string, int> dic = Enums.ToNameKeyPaires(typeof(ActionTypes));
            return currentStep.wfActions.Select(action => (ActionTypes)dic[action.ActionTypeID.ToString()]);
        }

        /// <summary>
        /// Gets the process instance ID by entity.
        /// </summary>
        /// <param name="entityID">The entity ID.</param>
        /// <returns></returns>
        public Guid GetProcessInstanceIDByEntity(Guid entityID)
        {
            return _WorkFlow.wfProcessInstances.SingleOrDefault(i => i.EntityID == entityID).ProcessInstanceID;
        }

        /// <summary>
        /// Gets the process instance ID by entity.
        /// </summary>
        /// <param name="entityID">The entity ID.</param>
        /// <returns></returns>
        public IwfProcessInstance GetProcessInstanceByEntity(Guid entityID)
        {
            return _WorkFlow.wfProcessInstances.SingleOrDefault(i => i.EntityID == entityID);
        }

        /// <summary>
        /// Gets all loans in certain step.
        /// </summary>
        /// <param name="Step">The step from type Steps enum</param>
        /// <returns></returns>
        public IEnumerable<Guid> GetInStep(Steps Step)
        {
            var stepID = Enums.ToString(Step).ToGuid();
            return (from processInstance in this._WorkFlow.wfProcessInstances
                    where (from stepInst in processInstance.wfStepInstances
                           orderby stepInst.CreationDate descending
                           select stepInst).First().StepID == stepID
                    select processInstance.EntityID).OfType<Guid>();
        }

        /// <summary>
        /// Gets the instances in step.
        /// </summary>
        /// <param name="Step">The step.</param>
        /// <returns></returns>
        public IEnumerable<IwfStepInstance> GetInstancesInStep(Steps Step)
        {
            var stepId = Enums.ToString(Step).ToGuid();


            return (from processInstance in this._WorkFlow.wfProcessInstances
                    let lastStep = (from stepInst in processInstance.wfStepInstances
                                    orderby stepInst.CreationDate descending
                                    select stepInst).First()
                    where lastStep.StepID.Equals(stepId)
                    select lastStep);
        }
    }
}
